// This file is a part of Funky C++.
// Copyright (C) 2011  Valentin David
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
#ifndef CLIENT_HH
# define CLIENT_HH

# include <jack/port.hh>
# include <jack/exception.hh>
# include <forward_list>
# include <jack/midi_event.hh>
# include <string>

namespace jack {
  struct client {
  private:
    template <typename Sample>
    friend struct port;
    friend struct port_handler;

    ::jack_client_t* handle;
    bool on;

    int member_callback(::jack_nframes_t nframes) {
      for (auto i = ports.begin();
	   i != ports.end(); ++i) {
	(*i)->callback(nframes);
      }
      for (auto i = midi_ports.begin();
	   i != midi_ports.end(); ++i) {
	(*i)->callback(nframes);
      }
      return 0;
    }

    static int static_callback(::jack_nframes_t nframes, void* data) {
      client* that = reinterpret_cast<client*>(data);
      return that->member_callback(nframes);
    }

    void set_off() {
      on = false;
    }

    static void static_set_off(void *data) {
      client* that = reinterpret_cast<client*>(data);
      that->set_off();
    }

    std::forward_list<port<jack_default_audio_sample_t>*> ports;
    std::forward_list<port<jack_midi_event_t>*> midi_ports;

  public:
    void add(port<jack_default_audio_sample_t>& port) {
      ports.push_front(&port);
    }

    void remove(port<jack_default_audio_sample_t>& port) {
      auto previous = ports.begin();
      for (auto i = previous;
	   i != ports.end();) {
	if (*i == &port) {
	  if (i == previous) {
	    ports.pop_front();
	    return ;
	  }
	  else {
	    ports.erase_after(previous);
	    return ;
	  }
	}
	previous = i++;
      }
    }

    void add(port<jack_midi_event_t>& port) {
      midi_ports.push_front(&port);
    }

    void remove(port<jack_midi_event_t>& port) {
      auto previous = midi_ports.begin();
      for (auto i = previous;
	   i != midi_ports.end();) {
	if (*i == &port) {
	  if (i == previous) {
	    midi_ports.pop_front();
	    return ;
	  }
	  else {
	    midi_ports.erase_after(previous);
	    return ;
	  }
	}
	previous = i++;
      }
    }

    client(const client&) = delete;

    client(std::string&& s, ::JackOptions options = ::JackNullOption)
      : on(true) {
      ::JackStatus status;
      handle = ::jack_client_open(s.c_str(), options, &status);
      exception::status(status);
      exception::zero
	(::jack_set_process_callback(handle,
				     static_callback,
				     reinterpret_cast<void*>(this)));
      ::jack_on_shutdown(handle,
			 static_set_off, reinterpret_cast<void*>(this));
    }

    bool is_on() const {
      return on;
    }

    port<jack_midi_event_t>*
    register_port(port<jack_midi_event_t>*,
		  std::string&& name,
		  unsigned long flags,
		  unsigned long buffer_size) {
      ::jack_port_t* result =
	::jack_port_register(handle,
			     name.c_str(), JACK_DEFAULT_MIDI_TYPE,
			     flags, buffer_size);
      if (result == NULL)
	throw exception((::JackStatus)0);
      port<jack_midi_event_t>* ret = new port<jack_midi_event_t>(this, result);
      midi_ports.push_front(ret);
      return ret;
    }

    port<jack_default_audio_sample_t>*
    register_port(port<jack_default_audio_sample_t>*,
		  std::string&& name,
		  unsigned long flags,
		  unsigned long buffer_size) {
      ::jack_port_t* result =
	::jack_port_register(handle,
			     name.c_str(), JACK_DEFAULT_AUDIO_TYPE,
			     flags, buffer_size);
      if (result == NULL)
	throw exception((::JackStatus)0);
      port<jack_default_audio_sample_t>* ret
	= new port<jack_default_audio_sample_t>(this, result);
      ports.push_front(ret);
      return ret;
    }

    template <typename T>
    port<T>*
    register_port(std::string&& name,
		  unsigned long flags,
		  unsigned long buffer_size) {
      return register_port((port<T>*)0,
			   std::move(name),
			   flags, buffer_size);
    }

    void activate() {
      exception::zero(::jack_activate(handle));
    }

    void deactivate() {
      exception::zero(::jack_deactivate(handle));
    }

    ~client() {
      exception::zero(::jack_client_close(handle));
    }
  };

}

# include "port.tcc"
#endif
